using System;
using System.Collections.Generic;
using System.Linq;
using Urs.Core;
using Urs.Core.Caching;
using Urs.Core.Data;
using Urs.Data.Domain.Common;
using Urs.Data.Domain.Orders;
using Urs.Data.Domain.Shipping;
using Urs.Data.Domain.Configuration;
using Urs.Services.Stores;
using Urs.Services.Localization;
using Urs.Services.Logging;
using Urs.Services.Plugins;

namespace Urs.Services.Shipping
{
    public partial class ShippingService : IShippingService
    {
        #region Constants

        private const string SHIPPINGMETHODS_BY_ID_KEY = "Urs.shippingMethod.id-{0}";
        private const string SHIPPINGMETHODS_PATTERN_KEY = "Urs.shippingMethod.";

        #endregion

        #region Fields

        private readonly IRepository<ShippingMethod> _shippingMethodRepository;
        private readonly ICacheManager _cacheManager;
        private readonly ILogger _logger;
        private readonly IWorkContext _workContext;
        private readonly IGoodsSpecParser _goodsSpecParser;
        private readonly ILocalizationService _localizationService;
        private readonly ShippingSettings _shippingSettings;
        private readonly IPluginFinder _pluginFinder;
        private readonly ShoppingCartSettings _shoppingCartSettings;

        #endregion

        #region Ctor

        public ShippingService(ICacheManager cacheManager,
            IRepository<ShippingMethod> shippingMethodRepository,
            ILogger logger,
            IWorkContext workContext,
            IGoodsSpecParser goodsSpecParser,
            ILocalizationService localizationService,
            ShippingSettings shippingSettings,
            IPluginFinder pluginFinder,
            ShoppingCartSettings shoppingCartSettings)
        {
            this._cacheManager = cacheManager;
            this._shippingMethodRepository = shippingMethodRepository;
            this._logger = logger;
            this._workContext = workContext;
            this._goodsSpecParser = goodsSpecParser;
            this._localizationService = localizationService;
            this._shippingSettings = shippingSettings;
            this._pluginFinder = pluginFinder;
            this._shoppingCartSettings = shoppingCartSettings;
        }

        #endregion

        #region Methods

        #region Shipping rate computation methods

        public virtual IList<IShippingRateMethod> LoadActiveShippingRateComputationMethods()
        {
            return LoadAllShippingRateComputationMethods()
                   .Where(provider => _shippingSettings.ActiveShippingRateComputationMethodSystemNames.Contains(provider.PluginDescriptor.SystemName, StringComparer.InvariantCultureIgnoreCase))
                   .ToList();
        }

        public virtual IShippingRateMethod LoadShippingRateComputationMethodBySystemName(string systemName)
        {
            var descriptor = _pluginFinder.GetPluginDescriptorBySystemName<IShippingRateMethod>(systemName);
            if (descriptor != null)
                return descriptor.Instance<IShippingRateMethod>();

            return null;
        }

        public virtual IList<IShippingRateMethod> LoadAllShippingRateComputationMethods()
        {
            return _pluginFinder.GetPlugins<IShippingRateMethod>().ToList();
        }

        #endregion

        #region Shipping methods


        public virtual void DeleteShippingMethod(ShippingMethod shippingMethod)
        {
            if (shippingMethod == null)
                throw new ArgumentNullException("shippingMethod");

            _shippingMethodRepository.Delete(shippingMethod);

            _cacheManager.RemoveByPattern(SHIPPINGMETHODS_PATTERN_KEY);

        }

        public virtual ShippingMethod GetShippingMethodById(int shippingMethodId)
        {
            if (shippingMethodId == 0)
                return null;

            string key = string.Format(SHIPPINGMETHODS_BY_ID_KEY, shippingMethodId);
            return _cacheManager.Get(key, () =>
            {
                var shippingMethod = _shippingMethodRepository.GetById(shippingMethodId);
                return shippingMethod;
            });
        }

        public virtual IList<ShippingMethod> GetAllShippingMethods()
        {
            var query = from sm in _shippingMethodRepository.Table
                        orderby sm.DisplayOrder
                        select sm;
            var shippingMethods = query.ToList();
            return shippingMethods;
        }

        public virtual void InsertShippingMethod(ShippingMethod shippingMethod)
        {
            if (shippingMethod == null)
                throw new ArgumentNullException("shippingMethod");

            _shippingMethodRepository.Insert(shippingMethod);

            _cacheManager.RemoveByPattern(SHIPPINGMETHODS_PATTERN_KEY);

        }

        public virtual void UpdateShippingMethod(ShippingMethod shippingMethod)
        {
            if (shippingMethod == null)
                throw new ArgumentNullException("shippingMethod");

            _shippingMethodRepository.Update(shippingMethod);

            _cacheManager.RemoveByPattern(SHIPPINGMETHODS_PATTERN_KEY);
        }

        #endregion

        #region Workflow

        public virtual decimal GetShoppingCartItemWeight(ShoppingCartItem shoppingCartItem)
        {
            if (shoppingCartItem == null)
                throw new ArgumentNullException("shoppingCartItem");
            decimal weight = decimal.Zero;
            if (shoppingCartItem.Goods != null)
            {
                decimal attributesTotalWeight = decimal.Zero;

                if (!String.IsNullOrEmpty(shoppingCartItem.AttributesXml))
                {
                    var combination = _goodsSpecParser.FindGoodsSpecCombination(shoppingCartItem.Goods, shoppingCartItem.AttributesXml);
                    if (combination != null)
                        attributesTotalWeight += combination.Weight;
                }
                weight = shoppingCartItem.Goods.Weight + attributesTotalWeight;
            }
            return weight;
        }

        public virtual decimal GetShoppingCartItemTotalWeight(ShoppingCartItem shoppingCartItem)
        {
            if (shoppingCartItem == null)
                throw new ArgumentNullException("shoppingCartItem");

            decimal totalWeight = GetShoppingCartItemWeight(shoppingCartItem) * shoppingCartItem.Quantity;
            return totalWeight;
        }

        public virtual decimal GetShoppingCartTotalWeight(IList<ShoppingCartItem> cart)
        {
            decimal totalWeight = decimal.Zero;
            foreach (var shoppingCartItem in cart)
                totalWeight += GetShoppingCartItemTotalWeight(shoppingCartItem);

            return totalWeight;
        }

        public virtual GetShippingOptionRequest CreateShippingOptionRequest(IList<ShoppingCartItem> cart,
            Address shippingAddress)
        {
            var request = new GetShippingOptionRequest();
            request.Items = new List<ShoppingCartItem>();
            foreach (var sc in cart)
                if (sc.IsShipEnabled)
                    request.Items.Add(sc);
            request.ShippingAddress = shippingAddress;
            return request;

        }

        public virtual GetShippingOptionResponse GetShippingOptions(IList<ShoppingCartItem> cart,
            Address shippingAddress, string allowedShippingRateComputationMethodSystemName = "")
        {
            if (cart == null)
                throw new ArgumentNullException("cart");

            var result = new GetShippingOptionResponse();

            var getShippingOptionRequest = CreateShippingOptionRequest(cart, shippingAddress);
            var shippingRateComputationMethods = LoadActiveShippingRateComputationMethods()
                .Where(srcm =>
                    String.IsNullOrWhiteSpace(allowedShippingRateComputationMethodSystemName) ||
                    allowedShippingRateComputationMethodSystemName.Equals(srcm.PluginDescriptor.SystemName, StringComparison.InvariantCultureIgnoreCase))
                .ToList();
            if (shippingRateComputationMethods.Count == 0)
            {
                return result;
            }
            foreach (var srcm in shippingRateComputationMethods)
            {
                var getShippingOptionResponse = srcm.GetShippingOptions(getShippingOptionRequest);
                foreach (var so2 in getShippingOptionResponse.ShippingOptions)
                {
                    so2.ShippingRateMethodSystemName = srcm.PluginDescriptor.SystemName;
                    if (_shoppingCartSettings.RoundPricesDuringCalculation)
                        so2.Rate = Math.Round(so2.Rate, 2);
                    result.ShippingOptions.Add(so2);
                }

                if (!getShippingOptionResponse.Success)
                {
                    foreach (string error in getShippingOptionResponse.Errors)
                    {
                        result.AddError(error);
                        _logger.Warning(string.Format("Shipping ({0}). {1}", srcm.PluginDescriptor.FriendlyName, error));
                    }
                }
            }

            if (_shippingSettings.ReturnValidOptionsIfThereAreAny)
            {
                if (result.ShippingOptions.Count > 0 && result.Errors.Count > 0)
                    result.Errors.Clear();
            }

            if (result.ShippingOptions.Count == 0 && result.Errors.Count == 0)
                result.Errors.Add(_localizationService.GetResource("Checkout.ShippingOptionCouldNotBeLoaded"));

            result.Success = true;
            return result;
        }

        #endregion

        #endregion
    }
}
